
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>Title: Serveur.Protocol </p>
 *
 * <p>
 * 	Description: La classe Protocol du package Serveur  
 *  Decrit tout le protocole de communication
 * </p>
 *
 * <p>Copyright: (c) 2008 FSSIM</p>
 *
 * <p>Company: FSSImock</p>
 *
 * @author Antoine Farnault
 * @author Augustin Couette
 * @author Sebastien Oger
 * @version 1.1
 */
public class Protocol {
	
	private static final String SEPARATOR = ":";
	
	///TEST 
	static Carte c1 =  new Carte("2+3*5", "25", "17", "Je passe", "Calculatrice SVP", "rB");
	static Carte c2 =  new Carte("Qu'est ce qu'un informaticien", "Un autiste", "Un genie", "Nous !", "Vous ?", "rC");
	static Carte c3 =  new Carte("Qu'est ce qu'un chef de projet", "Quelqu'un qui sert a rien ", "J.A", "On sait toujours pas", "Un cerveau", "rD");
	static Carte c4 =  new Carte("Que veut dire POO", "Programmation Over Over", "Programmation Orientee Objet", "Patate Ou Oeuf", ".....", "rB");
	static Carte c5 =  new Carte("Quelle est la capitale de l'Australie", "Sidney", "Melbourne", "Camberra", "les deux", "rC");
	
	
	
	
	/**
	 * Methode de parsage du protocol
	 * @param entree le flux d'entree
	 * @param from 1: from joueur    0: from serveur 
	 * @throws Exception
	 */
	public static void parser(BufferedReader entree, Client client) throws IOException, ProtocolException {
		HashMap<String, String> h = new HashMap<String, String>(); 
		String s;
		while((s=entree.readLine())!= null ){ 
			System.out.println(s);
			if (s.equals("FIN")){
				break;
			}
			try{
				Pattern pattern = Pattern.compile("^(\\w*)"+SEPARATOR+"(.*)$");
				Matcher matcher = pattern.matcher(s);
				if (matcher.find()) {
					String action = matcher.group(1);
					String values = matcher.group(2);
					h.put(action, values);
				} else {
					throw new ProtocolException("Non respect du protocol : "+s, h);
				}
			}catch(Exception e){
				System.out.println("Erreur de parsing, ne peut splitter sur ':'");
				e.printStackTrace();
				return;
			}
		}
		protocolHandler(h, client);
	}
	
	/**
	 * Methode qui appelle les differentes fonctions de communication
	 * @param h la hashmap parse par le protocol
	 * @param from 1: from joueur    0: from serveur 
	 * @throws ProtocolException
	 */
	private static void protocolHandler(HashMap<String, String> h, Client c) throws ProtocolException{
		String type = h.get("type");
		
		if (type.equalsIgnoreCase("chat")){
			rcv_chat(h.get("data"), c, h.get("table"),h.get("couleur"));		
			return;
		}
        if(type.equalsIgnoreCase("De")){
			asw_lancerDe(h.get("pseudo"),h.get("table"), c);
			return;
		}

		if(type.equalsIgnoreCase("table")){
			if(h.get("status").equals("ajout")){
				int capacite = Integer.parseInt(h.get("capacite"));
				boolean privee = false;
				if(h.get("privee").equalsIgnoreCase("true")){
					privee = true;
				}
				rcv_table(h.get("proprietaire"), h.get("nom"),h.get("theme"),capacite,h.get("pass"),privee);
				return;
			}
			if(h.get("status").equals("join")){
				rcv_joinTable(h.get("pseudo"), h.get("table"), c);
				return;
			}
			if(h.get("status").equals("leave")){
				rcv_leaveTable(h.get("pseudo"), h.get("table"), c);
				return;
			}
		}
		if(type.equalsIgnoreCase("auth")){
			rcv_otp(h.get("otp"), c);
			return;
		}
		if(type.equalsIgnoreCase("reponse")){
			rcv_reponse(h.get("reponse"), h.get("table"), c, h.get("camembert"));
			return;
		}
		if(type.equalsIgnoreCase("case")){
			rcv_case(c, h.get("id"), h.get("table"));
			return;
		}
	}
	
	/**
	 * On est serveur et on recoit une communication de type chat
	 * @param s les donnee que l'on recoit
	 * @param pseudo le pseudo emetteur de s
	 */
	private static void rcv_chat(String s, Client c,String salle,String couleur){
		Serveur.envoieATous(s, c.name,salle,couleur);
	}

	/**
	 * On est serveur et on envoie une communication de type chat
	 * @param p le printwiter du client
	 * @param s les donnees a envoyer
	 * @param pseudo le pseudo emeteur des donnees
	 * @throws ProtocolException 
	 */
	public static void snd_chat(PrintWriter p,String prefix, String s, String pseudo,String table,String couleur) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "chat");
		hp.put("data", s);
		hp.put("prefix", prefix);
		hp.put("from", pseudo);
		hp.put("table", table);
		hp.put("couleur", couleur);
		formatToProtocol(p, hp);
	}

	/**
	 * Le serveur ajoute un joueur a la liste
	 * @param p
	 * @param pseudo
	 * @param status
	 * @param table
	 * @param couleur
	 * @throws ProtocolException 
	 */
	public static void snd_joueur(PrintWriter p, String pseudo, String status, String couleur) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "joueur");
		hp.put("pseudo", pseudo);
		hp.put("status", status);
		hp.put("couleur", couleur);
		formatToProtocol(p,hp);
	}
	
	/**
	 * Le serveur recoit la notification de creation d'une table par pseudo
	 * @param pseudo
	 * @param nom
	 * @param theme
	 * @param nombreJoueurs
	 * @param capacite
	 * @param pass
	 * @param privee
	 */
	private static void rcv_table(String pseudo,String nom,String theme,int capacite,String pass,boolean privee){
		String msg;
		if(privee){
			msg = pseudo+" vient de creer la table privee "+nom;
		} else {
			msg = pseudo+" vient de creer la table "+nom;
		}
		Serveur.envoieATous(msg, "Serveur","Salle",Serveur.couleur);
		Serveur.tables.add(new Table(nom, theme, pseudo,capacite, pass, privee));
		
		//envoi de la tables a tout le monde
		for(int i=0;i<Serveur.clients.size();i++){
			try {
				snd_table(Serveur.clients.get(i).pw,pseudo,nom,theme,0, capacite,pass,privee);
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * On est serveur et on envoie aux clients la notification de creation de table
	 * @param p
	 * @param pseudo
	 * @param nom
	 * @param theme
	 * @param nombreJoueur
	 * @param capacite
	 * @param pass
	 * @param privee
	 * @throws ProtocolException 
	 */
	public static void snd_table(PrintWriter p,String pseudo, String nom,
			String theme,int nombreJoueurs ,int capacite, String pass,boolean privee) throws ProtocolException {
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "table");
		hp.put("status", "ajout");
		hp.put("nom", nom);
		hp.put("pseudo", pseudo);
		hp.put("theme", theme);
		hp.put("nombreJoueurs", nombreJoueurs+"");
		hp.put("capacite", ""+capacite);
		hp.put("pass", pass);
		hp.put("privee", ""+privee);
		formatToProtocol(p,hp);
	}
	/**
	 * Envoie de la liste des tables a la connexion d'un client
	 * @param p
	 * @throws ProtocolException 
	 */
	public static void snd_allTable(PrintWriter p) throws ProtocolException {
		Iterator<Table> it = Serveur.tables.iterator();
		while(it.hasNext()){
			Table tmp = it.next();
			snd_table(p, tmp.proprietaire, tmp.nom, tmp.theme,tmp.clients.size(), tmp.capacite, tmp.motDePasse,tmp.privee);
		}
	}
	/**
	 * Le serveur recoit une notification de client qui se connecte a une table
	 * @param nomTable
	 */
	private static void rcv_joinTable(String pseudo,String nomTable, Client c){
		Serveur.envoieATous(pseudo+" a rejoint la table "+nomTable, "Serveur", "salle",Serveur.couleur);
		Serveur.envoieATous(pseudo+" rejoint la table", "Serveur", nomTable,Serveur.couleur); // envoie que pour cette table
		
		
		Table t = Table.getTableByName(nomTable);
		t.clients.add(c);
		c.tables.add(t);
		if(t.clients.size() >= t.capacite){
			Serveur.lancerJeu(t);
		}

		// Mis a jour des donnees de la table pour tous
		for (Iterator<Client> iterator = Serveur.clients.iterator(); iterator.hasNext();) {
			Client client = iterator.next();
			try {
				Protocol.snd_joinTable(client,pseudo, t);// on envoie a tout le monde 'pseudo' a rejoint t
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}
		
		//Mis a jour de la table pour un
		for (Iterator<Client> iterator = Serveur.clients.iterator(); iterator.hasNext();) {
			Client client = iterator.next();
			try {
				if (client.equals(c))
					continue;
				if (client.tables.contains(t))
					Protocol.snd_joinTable(c,client.name, t); // on envoie a celui qui vient de joindre tous
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}
		
	}
	/**
	 * Le serveur recoit une notification de client qui se connecte a une table
	 * @param nomTable
	 */
	public static void rcv_leaveTable(String pseudo,String nomTable, Client c){
		Serveur.envoieATous(pseudo+" a quitter la table "+nomTable, "Serveur", "salle",Serveur.couleur);
		Serveur.envoieATous(pseudo+" quitte la table", "Serveur", nomTable,Serveur.couleur); // envoie que pour cette table
		
		//on retire le pseudo a la liste de clients de la table
		//Serveur.updateConnectedJoueurs(c, nomTable);

		Iterator<Table> it = Serveur.tables.iterator();
		while(it.hasNext()){
			Table tmp = it.next();
			if(tmp.nom.equals(nomTable)){
				c.tables.remove(tmp);
				break;
			}
		}
		// Mis a jour des donnees de la table pour tous
		for (Iterator<Client> iterator = Serveur.clients.iterator(); iterator.hasNext();) {
			Client client = iterator.next();
			try {
				Protocol.snd_leaveTable(client, nomTable);
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}
	}
	/**
	 * Envoie a un client le fait que qlq un quitte une table 
	 * @param c
	 * @param t
	 * @throws ProtocolException 
	 */
	public static void snd_leaveTable(Client c, String nomTable) throws ProtocolException{
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "table");
		hp.put("status","leave");
		hp.put("table", nomTable);
		hp.put("pseudo", c.name);
		formatToProtocol(c.pw, hp);
	}
	
	/**
	 * Envoie a un client le fait que qlq un rejoint une table 
	 * @param c
	 * @param t
	 * @throws ProtocolException 
	 */
	public static void snd_joinTable(Client c,String pseudo, Table t) throws ProtocolException{
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "table");
		hp.put("status","join");
		hp.put("table", t.nom);
		hp.put("pseudo", pseudo);
		formatToProtocol(c.pw, hp);
	}

	/**
	 * Le serveur dit que ce joueur a le droit de se connecter
	 * @param p
	 * @throws ProtocolException 
	 */
	public static void snd_connectOk(PrintWriter p) throws ProtocolException {
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "connect");
		hp.put("status", "ok");
		formatToProtocol(p,hp);
	}
	
	/**
	 * Envoie les donnees dans le format du protocol en verifiant 
	 * qu'elles sont bien formees.
	 * @param p 
	 * @param hp
	 */
	public static void formatToProtocol(PrintWriter p,HashMap<String, String> hp ) throws ProtocolException {
		try{
			hp.get("type");
		}catch(Exception e){
			throw new ProtocolException("Il n'y a pas de 'type' pour cet envoie de donnee");
		}
		for(Entry<String, String> entry : hp.entrySet()) {
		    String cle = entry.getKey();
		    String valeur = entry.getValue();
		    
		    // verif sur la cle et la valeur
		    if(cle.matches(":")){ // la regexp du parser prend le 1er ':' donc il en faut pas ici
		    	throw new ProtocolException("La cle contient ':'");
		    }
		    // Ecriture des donnees
		    p.println(cle+":"+valeur);
		}
		p.println("FIN");
		p.flush();
	}

	/**
	 * Le serveur repond a la demande d'un lancer de de 
	 * @param s
	 * @param pseudo
	 * @param table
	 * @throws ProtocolException 
	 */
	public static void asw_lancerDe(String pseudo, String table, Client c) throws ProtocolException{
		//TODO tester que c'est le joueur dont c'est le tour
		int score = Serveur.lancerDe();
		Serveur.envoieATous(pseudo+" a fait "+score, "serveur", table,Serveur.couleur);
		snd_de(""+score,table, c);
	}
	
    /**
     * Envoie du challenge au client
     * @param c
     * @throws ProtocolException
     */
    public static void snd_otp(Client c) throws ProtocolException{
    	int sequence = Auth.makeSequence();
    	String seed = Auth.makeSeed();
    	c.sequence = sequence;
    	c.seed = seed;
    	HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "auth");
		hp.put("sequence", ""+sequence);
		hp.put("seed", ""+seed);
		formatToProtocol(c.pw,hp);
    }
    
    /**
     * Reception du challenge resolu par le client
     * @param otp
     * @param c
     */
	private static void rcv_otp(String otp, Client c){
    	try {
    		//Verif dans la base de donne TODO
    		byte[] b = Auth.compute(c.seed,"toto", c.sequence); // FIXME hardcoded
    		String hash = new BigInteger(b).toString(16);
			
    		if (hash.equalsIgnoreCase(otp)){ // Identification OK
				Serveur.envoieATous(c.name+" vient de se connecter", "Serveur","salle",Serveur.couleur);
    			Protocol.snd_connectOk(c.pw);
    			Serveur.updateConnectedJoueurs(c);
    			Serveur.insererJoueur(c);
				Protocol.snd_allTable(c.pw);
    		}else{ // Identification NON OK
    			System.err.println("Identification NON OK");
    			try {
					c.th.connexion.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
    		}
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (ProtocolException e) {
			e.printStackTrace();
		}
    }
    
	
	public static void snd_tour(Table table) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "tour");
		hp.put("pseudo", table.tour.name);
		hp.put("table", ""+table.nom);
		formatToProtocol(table.tour.pw, hp);
	}
	
	private static void rcv_reponse(String reponse, String table, Client c, String camembert){
		Table t = Table.getTableByName(table);
		
		if(reponse.equalsIgnoreCase("timeout")){
			Serveur.envoieATous(t.tour.name+" n'a pas repondu a temps", "Serveur", table, Serveur.couleur);
			try {
				snd_reponse(t, "timeout",camembert);
				return;
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}else{
			Serveur.envoieATous(" a repondu `"+t.tour.carte.getReponse(reponse)+"`", "Serveur", table, Serveur.couleur);
		}
		if(c.carte.reponse.equalsIgnoreCase(reponse)){
			try {
				snd_reponse(t, "ok",camembert);
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}else{
			try {
				snd_reponse(t, "faux",camembert);
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}
	}
	
	private static void snd_reponse(Table t, String ok,String camembert) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		
		hp.put("camembert", camembert);
		hp.put("type", "reponse");
		hp.put("reponse", ok);
		hp.put("table", ""+t.nom);
		formatToProtocol(t.tour.pw, hp);
		
		if(ok.equalsIgnoreCase("ok")){
			snd_tour(t);
			Serveur.envoieATous(t.tour.name+" a bien repondu, il rejoue", "Serveur", t.nom, "000,000,255");
		}
		if(ok.equalsIgnoreCase("faux")){
			Serveur.envoieATous(t.tour.name+" a fait faux", "Serveur", t.nom, "255,000,000");
			t.nextTour();
		}
		if(ok.equalsIgnoreCase("timeout")){
			t.nextTour();
		}
	}
	
     /**
     * Le serveur envoie au joueur son score au de
     * @param data
     * @param c
     * @throws ProtocolException 
     */
    public static void snd_de(String data, String table, Client c) throws ProtocolException{
    	HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "de");
		hp.put("score", ""+data);
		hp.put("table", ""+table);
		formatToProtocol(c.pw, hp);
    }
	/**
	 * Envoie une carte a un joueur
	 * @param p
	 * @param carte
	 * @param table
	 * @throws ProtocolException 
	 */
	public static void snd_carte(PrintWriter p, Carte carte, String table, String pseudo) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "carte");
		hp.put("question", carte.question);
		hp.put("table", table);
		hp.put("rA", carte.rA);
		hp.put("rB", carte.rB);
		hp.put("rC", carte.rC);
		hp.put("rD", carte.rD);
		hp.put("pseudo", pseudo);
		formatToProtocol(p, hp);
	}
	
	private static void rcv_case(Client c, String id, String table){
		Table t = Table.getTableByName(table);
		Iterator<Client> it = t.clients.iterator();
		
		Vector<Carte> cartes = new Vector<Carte>();
		cartes.add(c1);cartes.add(c2);cartes.add(c3);cartes.add(c4);cartes.add(c5);
		Random r = new Random();
		Carte carte= cartes.get(r.nextInt(cartes.size()));
		c.carte = carte;

		
		while(it.hasNext()){
			Client tmp = it.next();
			try {
				snd_case(tmp, id, t, c.couleur);
				snd_carte(tmp.pw,carte, table, c.name); //TODO
			} catch (ProtocolException e) {
				e.printStackTrace();
			}
		}
		
		
	}

	private static void snd_case(Client tmp, String id, Table t, String couleur) throws ProtocolException {
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "case");
		hp.put("table", t.nom);
		hp.put("id", id+"");
		hp.put("couleur", couleur);
		formatToProtocol(tmp.pw, hp);
	}
	
}












